<?php
/**
 *
 * @author Tomasz Ulanowski <tomasz@ulanowski.info>
 *
 */
class MyZendX_File_Transfer_Adapter_Uploadify extends Zend_File_Transfer_Adapter_Http
{
	/**
	 * @var Zend_File_Transfer_Adapter_Http
	 */
	protected $_backendTransferAdapter;
	
	/**
	 * @var string
	 */
	protected $_backendUploadFolder;
	
	/**
	 * @var string
	 */
	protected static $_defaultBackendUploadFolder;

	/**
	 * @var array
	 */
	protected $_backendTransferAdapterValidators;
	
	/**
	 * @var array
	 */
	protected static $_defaultBackendTransferAdapterValidators = array(
		'extension'=>array('jpg,jpeg,gif,png')
	);
	
	/**
	 * Time in seconds after which files from backend upload folder wiil be deleted
	 * 
	 * @var int
	 */
	protected $_backendUploadFolderExpiration = 86400; // 24h
	
	/**
	 * Propability of starting garbage collector
	 *  
	 * @var float
	 */
	protected $_gcPropability = 0.1;
	

	public function __destruct()
	{
		$this->_gc();
	}
	
	/**
	 * Receive file
	 *
	 * @param  mixed $options
	 * @return bool
	 */
	public function receive($files = null)
	{
		$check = $this->_getFiles($files);
		if ( empty($check) ) {
			return $this->_getHttpAdapter()->receive($files);
		}
		
		if (!$this->isValid($files)) {
			return false;
		}

		foreach ($check as $file => $content) {
			if (!$content['received']) {
				$directory   = '';
				$destination = $this->getDestination($file);
				if ($destination !== null) {
					$directory = $destination . DIRECTORY_SEPARATOR;
				}

				$filename = $directory . $content['name'];
				$rename   = $this->getFilter('Rename');
				if ($rename !== null) {
					$tmp = $rename->getNewName($content['tmp_name']);
					if ($tmp != $content['tmp_name']) {
						$filename = $tmp;
					}

					if (dirname($filename) == '.') {
						$filename = $directory . $filename;
					}

					$key = array_search(get_class($rename), $this->_files[$file]['filters']);
					unset($this->_files[$file]['filters'][$key]);
				}

				// Should never return false when it's tested by the upload validator
				$mv = isset($content['uploadify']) && $content['uploadify'] ? 'rename' : 'move_uploaded_file'; 
				if (!$mv($content['tmp_name'], $filename)) {
					if ($content['options']['ignoreNoFile']) {
						$this->_files[$file]['received'] = true;
						$this->_files[$file]['filtered'] = true;
						continue;
					}

					$this->_files[$file]['received'] = false;
					return false;
				}

				if ($rename !== null) {
					$this->_files[$file]['destination'] = dirname($filename);
					$this->_files[$file]['name']        = basename($filename);
				}

				$this->_files[$file]['tmp_name'] = $filename;
				$this->_files[$file]['received'] = true;
			}

			if (!$content['filtered']) {
				if (!$this->_filter($file)) {
					$this->_files[$file]['filtered'] = false;
					return false;
				}

				$this->_files[$file]['filtered'] = true;
			}
		}

		return true;
	}

	/**
	 * @param string $name
	 * @param string|array $filename
	 */
	public function addFiles($name, $filename)
	{
		if ( is_array($filename) ) {
			foreach($filename as $number => $file) {
				$subname = $name . '_' . $number . '_';
				$this->addFiles($subname, $file);
				$this->_files[$name]['multifiles'][$number] = $subname;
				$this->_files[$name]['name'] = $name; 
			}
		}
		else {
			$tmpName = $this->findTmpUploadedFile($filename);
			$this->_files[$name]['name'] = (null === $tmpName) ? false : substr($filename, 0, strrpos($filename, '-'));
			$this->_files[$name]['tmp_name'] = $tmpName; 
			 
			$this->_files[$name]['options']   = $this->_options;
			
			$this->_files[$name]['validated'] = false;
			$this->_files[$name]['received']  = false;
			$this->_files[$name]['filtered']  = false;
			
			$this->_files[$name]['type'] = (null === $tmpName) ? null : $this->_detectMimeType($this->_files[$name]);
			$this->_files[$name]['size'] = (null === $tmpName) ? null : $this->_detectFileSize($this->_files[$name]);
			
			$this->_files[$name]['validators'] = array_keys($this->_validators);
			$this->_files[$name]['filters'] = array_keys($this->_filters);
			
			$this->_files[$name]['uploadify'] = true;
		}
	}
	
	/**
	 * Constructs backend adapter to receive HTTP file upload
	 * 
	 * @return Zend_File_Transfer_Adapter_Http
	 */
	public function getBackendTransferAdapter()
	{
		if ( null === $this->_backendTransferAdapter ) {
			$this->_backendTransferAdapter = new Zend_File_Transfer_Adapter_Http();
			$this->_backendTransferAdapter->setValidators($this->getBackendTransferAdapterValidators());
			$filter = new Zend_Filter_File_Rename(array());
			foreach($this->_backendTransferAdapter->getFileInfo() as $fileinfo) {
				$filter->addFile(array(
					'source'	=> $fileinfo['tmp_name'],
					'target'	=> $this->_getBackendDestination($fileinfo),
					'overwrite'	=> true 
				));
			}
			$this->_backendTransferAdapter->addFilter($filter);
			
		}
		return $this->_backendTransferAdapter;
	}
	
	/**
	 * @param Array $fileinfo
	 * @return string
	 */
	private function _getBackendDestination($fileinfo)
	{
		$parts = explode('.', $fileinfo['name']);
		$filename = $fileinfo['name'] . '-' . uniqid() . '.' . end($parts);
		$uploadFolder = realpath($this->getBackendUploadFolder());
		$destination = $uploadFolder . DIRECTORY_SEPARATOR . $filename;
		return $destination;
	}
	
	/**
	 * @return string
	 */
	public function getBackendUploadFolder()
	{
		if (null == $this->_backendUploadFolder) {
			$this->_backendUploadFolder = self::getDefaultBackendUploadFolder();
		}
		return $this->_backendUploadFolder;
	}
	
	/**
	 * @param string $path
	 * @return MyZendX_File_Transfer_Adapter_Uploadify
	 */
	public function setBackendUploadFolder($path)
	{
		$this->_backendUploadFolder = $path;
		return $this;
	}

	/**
	 * @return string
	 */
	public static function getDefaultBackendUploadFolder()
	{
		if (null === self::$_defaultBackendUploadFolder) {
			self::$_defaultBackendUploadFolder = APPLICATION_PATH . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'uploads';
		}
		return self::$_defaultBackendUploadFolder;
	}
	
	/**
	 * @param string $path
	 * @return MyZendX_File_Transfer_Adapter_Uploadify
	 */
	public static function setDefaultBackendUploadFolder($path)
	{
		self::$_defaultBackendUploadFolder = $path;
	}

	/**
	 * @return array
	 */
	public function getBackendTransferAdapterValidators()
	{
		if (null === $this->_backendTransferAdapterValidators) {
			$this->_backendTransferAdapterValidators = self::getDefaultBackendTransferAdapterValidators();
		}
		return $this->_backendTransferAdapterValidators;
	}
	
	/**
	 * @param array $options
	 * @return MyZendX_File_Transfer_Adapter_Uploadify
	 */
	public function setBackendTransferAdapterValidators($options)
	{
		$this->_backendTransferAdapterValidators = $path;
		return $this;
	}

	/**
	 * @return array
	 */
	public static function getDefaultBackendTransferAdapterValidators()
	{
		return self::$_defaultBackendTransferAdapterValidators;
	}
	
	/**
	 * @param array $options
	 * @return MyZendX_File_Transfer_Adapter_Uploadify
	 */
	public static function setDefaultBackendTransferAdapterValidators($options)
	{
		self::$_defaultBackendTransferAdapterValidators = $options;
	}
	
	/**
	 * Searches for file in upload folder and returns full path
	 * 
	 * @param string $filename
	 * @return string|null
	 */
	public function findTmpUploadedFile($filename)
	{
		if (!$filename) {
			return null;
		}
		$path = $this->getBackendUploadFolder() . DIRECTORY_SEPARATOR . $filename;
		return is_readable($path) ? $path : null;
	}
    
    protected function _getFiles($files, $names = false, $noexception = false)
    {
    	if (!$names) {
		    foreach($this->_files as $name => &$file) {
		    	if (isset($file['uploadify']) && $file['uploadify'] && isset($file['validators'])) {
		    		$index = array_search('Zend_Validate_File_Upload', $file['validators']);
		    		if (false !== $index) {
		    			unset($file['validators'][$index]);
		    		}
		    	}
		    }
    	}
   		return parent::_getFiles($files, $names, $noexception);
    }
    
    
    
    /**
     * Garbage collector
     * 
     * Remove old files from tmp upload directory
     * @return void
     */
    protected function _gc()
    {
    	if (mt_rand(0, 100) > 100*$this->_gcPropability) {
    		return;
    	}
    	$expire = time() - $this->_backendUploadFolderExpiration;
    	$dir = $this->getBackendUploadFolder();
    	if(!$dir || strlen($dir) < 2) {
    		// safety guard
    		return;
    	}
    	foreach (new DirectoryIterator($dir) as $file) {
    		if (!$file->isDot() && !$file->isDir()) {
    			if ($file->getMTime() < $expire) {
   					unlink($file->getPathname());
    			}
    		}
    	}
    }

}

class MyZendX_File_Transfer_Adapter_MyZendX_Exception
{}

